Skip to content

SSE and Streaming Support POC #1422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 13, 2025
Merged

Conversation

evalstate
Copy link
Collaborator

@evalstate evalstate commented May 2, 2025

Update to enable:

  • Connection via SSE (e.g. Gradio endpoints)
  • Connection via HTTP Streaming
  • Maintains compatibility with original STDIO config format
  • Concatenates TextContent/TextResourceContents from Results; describes ImageContent/BlobResourceContents
  • SSE Server definition contains workaround for SSE client is not adding headers from requestInit? in _commonHeaders method modelcontextprotocol/typescript-sdk#436 to pass HF_TOKEN
  • Ability to connect to "Streaming HTTP" endpoints from the command line. use pnpm agent --url https://<host>/mcp. You can specify multiple endpoints with --url <url1> --url <url2>

image

@julien-c
Copy link
Member

ah btw i've "approved" for the CI to get run

@@ -23,7 +29,7 @@ export interface ChatCompletionInputMessageTool extends ChatCompletionInputMessa

export class McpClient {
protected client: InferenceClient;
protected provider: InferenceProviderOrPolicy | undefined;
protected provider: InferenceProvider | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll need to merge main as this was changed (very slightly) on main

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(meaning InferenceProviderOrPolicy is the new type – though i agree it's a mouthful) ie. you can just revert those changes here and below

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically what this does is we now accept some "virtual" providers like "auto" (which points to your preferred provider in your HF settings that serve the model in question)

cc @Wauplin @hanouticelina from our client SDK team

@@ -55,6 +55,7 @@
"dependencies": {
"@huggingface/inference": "workspace:^",
"@huggingface/tasks": "workspace:^",
"@modelcontextprotocol/sdk": "^1.9.0"
"@modelcontextprotocol/sdk": ">=1.11.2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@modelcontextprotocol/sdk": ">=1.11.2",
"@modelcontextprotocol/sdk": "^1.11.2",

better to "pin" an exact dependency imo

@@ -44,7 +45,7 @@ const askQuestionTool: ChatCompletionInputTool = {
const exitLoopTools = [taskCompletionTool, askQuestionTool];

export class Agent extends McpClient {
private readonly servers: StdioServerParameters[];
private readonly servers: (ServerConfig|StdioServerParameters)[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll want to run the linter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you're using VS Code, it should pick up the formatting config automatically, otherwise:

pnpm lint and pnpm format

Comment on lines 31 to 47
// Handle --url parameter: parse comma-separated URLs into ServerConfig objects
const urlIndex = process.argv.indexOf("--url");
if (urlIndex !== -1 && urlIndex + 1 < process.argv.length) {
const urlsArg = process.argv[urlIndex + 1];
const urls = urlsArg
.split(",")
.map((url) => url.trim())
.filter((url) => url.length > 0);

for (const url of urls) {
try {
SERVERS.push(urlToServerConfig(url));
} catch (error) {
console.error(`Error adding server from URL "${url}": ${error.message}`);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and i would still keep the ones above as default (the stdio ones) if nothing is passed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at least the playwright one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Longer term I've been thinking of splitting this into a @huggingface/tiny-agents subpackage, where contributors could be interested in pushing combination of MCP servers + models + providers, WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed here 8d287af , wanted to open a PR against your PR but a bit of a pain given it's on a fork (will give you Write rights to this repo @evalstate )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Longer term I've been thinking of splitting this into a @huggingface/tiny-agents subpackage, where contributors could be interested in pushing combination of MCP servers + models + providers, WDYT?

like the idea, would be good to get collections of useful agents in one place. should probably iterate a couple more times to settle the config etc.

@@ -55,6 +55,7 @@
"dependencies": {
"@huggingface/inference": "workspace:^",
"@huggingface/tasks": "workspace:^",
"@modelcontextprotocol/sdk": "^1.9.0"
"@modelcontextprotocol/sdk": "^1.11.2",
"vitest": "^3.1.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"vitest": "^3.1.2"

it's already a dependency at the root-level, did it not work as it was? cc @coyotte508

Comment on lines 28 to 31
if (path.endsWith("/sse")) {
type = "sse";
} else if (path.endsWith("/mcp")) {
type = "streamableHttp";
Copy link
Member

@julien-c julien-c May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow this is actually enforced by the spec that the URL needs to end with /mcp? quite a big convention push, but fine i guess

export type ServerConfig =
| { type: "stdio"; config: StdioServerParameters }
| { type: "sse"; config: SSEServerConfig }
| { type: "streamableHttp"; config: StreamableHTTPServerConfig };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| { type: "streamableHttp"; config: StreamableHTTPServerConfig };
| { type: "http"; config: StreamableHTTPServerConfig };

i think i would just rename this to "http"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(need to push the resolved .lock too)

@@ -170,7 +198,7 @@ export class McpClient {
const client = this.clients.get(toolName);
if (client) {
const result = await client.callTool({ name: toolName, arguments: toolArgs, signal: opts.abortSignal });
toolMessage.content = (result.content as Array<{ text: string }>)[0].text;
toolMessage.content = ResultFormatter.format(result as CallToolResult);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) i take this as a "bug" in the TS MCP SDK that we need to cast this as a CallToolResult, no?

Copy link
Member

@julien-c julien-c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so apart from streamableHttp => http and a handful of small nits, this looks good to me

Let's 🚢 it soon!

@julien-c julien-c marked this pull request as ready for review May 13, 2025 10:33
Copy link
Member

@julien-c julien-c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

marked as ready for review, give it a final look @evalstate and then we can merge it?

@evalstate evalstate merged commit 5f4b160 into huggingface:main May 13, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants